package net.animats.music;

import java.lang.Math;
import java.util.Map;
import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;

public class Note {
	private double frequency;
	// p = 69 + 12 × log2 (f / (440 Hz))
	private double midi; 
	private int octave;
	private int index;		// THE MAIN UNIQUE IDENTIFIER OF THE NOTE

	private int nameIndex;  // THIS IS A 0-12 INDEX FOR THE TYPE OF NOTE
                            // REGARDLESS OF OCTAVE FOR NAMING PURPOSES

    // THESE ARE JUST DEFAULT NAMES TO USE IF THE KEY IS NOT KNOW WHEN
    // THE KEY IS KNOWN THE MORE TECHNICALLY CORRECT NAME CAN BE USED
	private static String[][] names =   {
              {"A", "A#", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#"},
              {"A", "Bb", "B", "C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Ab"},
                                        };

	private static Map<Integer, Note> notes = new HashMap<Integer, Note>();

	public static void main (String[] args) {
		for (int counter = -57; counter < 43; counter++) {
			Note currentNote = getNote(counter);
			System.out.println("Note " + currentNote + " has frequency of " + currentNote.getFrequency());
		}

		Note currentNote = getNote("F#");
		System.out.println("Note " + currentNote + " has frequency of " + currentNote.getFrequency());

	}

	private Note (int _index) {
		index = _index;	

		// SET THE NOTE'S FREQUENCY USING THE FORMULA
		// f = (2 to the power of (n/12)) × 440 Hz
		double power = (double) index / (double) 12;
		frequency = Math.pow(2, power) * 440;

		// SET THE NOTE'S OFFSET (ESSENTIALLY THE NAME OF THE NOTE,
		// THE INTEGER IS USED AT THE INDEX OF THE names ARRAY.
		// SET THE NOTE'S OCTAVE BASED ON ITS INDEX ABOVE OR BELOW
		// MIDDLE A.
		if (index < 0) {
			nameIndex = 12 - Math.abs(index % 12);
			octave = ((index - 2) / 12) + 4;
		} else {
			nameIndex = Math.abs(index % 12);
			octave = ((index + 9) / 12) + 4;
		}
	}

	public static Note getNote(int _index) {
		if (notes.get(_index) == null) {
			// IF THE NOTE HAS NOT YET BEEN CREATED,
			// MAKE ONE AND RETURN IT
			Note newNote = new Note(_index);
			notes.put(_index, newNote);
			return (newNote);
		} else {
			// RETURN THE PREVIOUSLY CREATED NOTE
			return notes.get(_index);
		}
	}

	public static Note getNote(String _name) {
		int index = getNameOffset (_name);

		if (index == -1) {
			return null;
		}

		// TREAT THE nameOffset AS AN INDEX AS WE ARE
		// NOT CONCERNED ABOUT OCTAVE HERE, JUST
		// GETTING AN INSTANCE OF THE RIGHT NOTE
		// eg. "A" = OFFSET "0", AND INDEX 0 EQUALS 
		// THE NOTE "A4" -- HAPPILY, AN "A".

		if (notes.get(index) == null) {
			// IF THE NOTE HAS NOT YET BEEN CREATED,
			// MAKE ONE AND RETURN IT
			Note newNote = new Note(index);
			notes.put(index, newNote);
			return (newNote);
		} else {
			// RETURN THE PREVIOUSLY CREATED NOTE
			return notes.get(index);
		}
	}

    	public boolean equals(Object _note) {
		if (_note instanceof Note) {
			if (((Note)_note).getName().equals(getName())) {
				// THERE IS A BIT OF AN ISSUE HERE WHERE THE NOTES OF CHORDS SHOULD ALL BE
				// THE SAME OBJECT, BUT SOME ARE AN OCTAVE DIFFERENT DEPENDING ON HOW
				// THEY WERE CREATED, NOT SURE WHY
				//System.out.println(getName() + ": " + ((Note)_note).getIndex() + " =? " + getIndex());
				return true;
			} else {
				return false;
			}
		} else {
			return false;
		}
	}

	public static int getNameOffset (String _name) {
        for (int outer = 0; outer < 2; outer++) {
            for (int index = 0 ; index < names[outer].length; index++) {
                if (names[outer][index].equals(_name)) {
                    return index;
                }
            }
        }
		return -1;
	}

	public String toString() {
		return (names[0][nameIndex] + octave);
	}

    public String toString(Key _key) {
        // THE KEY OF THE SONG IS KNOWN, SO USE THE CORRECT NAME FOR THE NOTE
		return (names[_key.getNamingIndex()][nameIndex] + octave);
	}

	public String getLabel() {
		return names[0][nameIndex];
	}

	public String getName() {
		return names[0][nameIndex];
	}

   	public String getName(Key _key) {
        // THE KEY OF THE SONG IS KNOWN, SO USE THE CORRECT NAME FOR THE NOTE
		return names[_key.getNamingIndex()][nameIndex];
	}

    public int getNameIndex() {
        return nameIndex;
    }
    
	static public String[] getNoteNamesArray() {
		return names[0];
	}

	static public List getNoteNames() {
		return Arrays.asList(names);
	}

	public int getIndex() {
		return index;
	}

	public double getFrequency() {
		return frequency;
	}
}
